Análisis de Negocio¶
El desarrollo de este Notebook se centra en analizar el comportamiento de un negocio digital en línea que conecta pequeñas empresas de Colombia, a través del uso de un sólo canal y un sólo contrato. Los comerciantes pueden vender sus productos a través de una tienda en línea y enviarlo directamente a los clientes a través de múltiples empresas logísticas aliadas. Además, luego de que un cliente compra el producto en la tienda el línea, el vendedor recibe una notificación y permite finalizar la transacción. Por otra parte, una vez que el cliente recibe el producto, o la fecha estimada de entrega se vence, el cliente recibe una encuesta de satisfacción por correo electrónico donde puede evaluar la experiencia de compra y agregar algunos comentarios.
Para el análisis vamos a emplear un conjunto de datos que contiene información de alrededor 10k pedidos realizados entre los años 2016 a 2018 que involucran múltiples comercios en Colombia, en este conjunto de datos podemos encontrar el estado del pedido, el precio, el pago, el desempeño del flete, la ubicación del cliente, los atributos del producto y finalmente las reseñas escritas por los clientes.
Objetivo:¶
El onjetivo principal es identificar las principales características sobre envíos, precios, valor de fletes y múltiples variables adicionales con el fin de estudiar el comportamiento que presentan las ventas a través del negocio digital. De manera específica lograremos:
- Preparar el conjunto de datos y validar su calidad para emplear en el análisis propuesto.
- Identificar las principales características en los datos a través del uso de estadística univariada y estadística bivariada.
- Responder preguntas acerca del comportamiento de algunas variables del negocio.
- Transformar la información proporcionada por el conjunto de datos en representaciones visuales a través de la visualización de datos en Python con el objetivo de analizar la información y comunicar de manera más efectivas las conclusiones obtenidas.
Conocimientos aplicados¶
- Carga de archivos mediante Pandas.
- Preparación y validación de la calidad del conjunto de datos a emplear.
- Análisis exploratorio de datos.
- Estadística univariada.
- Visualización de datos con Python.
1. Importación de librerías y archivos¶
En las siguientes líneas de código vamos a importar las librerías a utilizar en el análisis de las características del negocio, así como los archivos que contienen el conjunto de datos.
#Importamos las liberías necesarias para realizar el análisis de interés.
import pandas as pd #librería para manejo de datos.
from datetime import date #librería para manejar tipo de datos datetime.
import numpy as np #librería para cálculos matemáticos.
#nos permite analizar gráficos a través del mouse y redimencionarlas a conveniencia.
import plotly.io as pio
pio.renderers.default='notebook'
import plotly.express as px
import random #para generar valores aleatorios
from IPython.display import IFrame #para incluir archivos html en el notebook.
#cargamos el conjunto de datos a emplear.
data_ordenes=pd.read_csv('Ordenes_productos_C1_M2.3.csv', sep=';')
data_ordenes.head()
| orden_id | order_item_id | producto_id | vendedor_id | fecha_envio_limite | precio | valor_flete | codigo_postal_vendedor | ciudad_vendedor | departamento_vendedor | nombre_categoria_producto | longitud_nombre_producto | longitud_descripcion_producto | cantidad_fotos_producto | peso_g_producto | longitud_cm_producto | altura_cm_producto | ancho_cm_producto | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 107500PO59A | A | PO59 | VE5389 | 7/04/2018 18:12 | 271.86 | 30.72 | 52435 | Mallama | Nariño | Productos ecoamigables | 6.0 | 7.0 | 27.0 | 2486.0 | 17.0 | 11.0 | 14.0 |
| 1 | 37493PS22B | B | PS22 | VE1558 | 20/10/2017 9:07 | 115.73 | 4.68 | 52203 | Colon | Nariño | Carnicería | 10.0 | 31.0 | 20.0 | 256.0 | 43.0 | 2.0 | 21.0 |
| 2 | 28050PK20B | B | PK20 | VE9159 | 17/08/2017 8:15 | 432.99 | 82.70 | 66001 | Pereira | Risaralda | Deportes | 25.0 | 5.0 | 4.0 | 5270.0 | 9.0 | 27.0 | 29.0 |
| 3 | 52187PA10A | A | PA10 | VE3159 | 23/09/2017 23:27 | 108.38 | 35.39 | 52435 | Mallama | Nariño | Electrodomésticos | 10.0 | 1.0 | 6.0 | 734.0 | 46.0 | 48.0 | 22.0 |
| 4 | 84639PR12A | A | PR12 | VE5090 | 7/01/2018 11:50 | 51.50 | 11.10 | 73001 | Ibague | Tolima | Frutas y verduras | 23.0 | 16.0 | 35.0 | 884.0 | 45.0 | 26.0 | 18.0 |
#validamos en tamaño de nuestro conjunto de datos.
data_ordenes.shape
(10134, 18)
El conjunto de datos: observamos que el conjunto de datos posee un total de 10134 registros y 18 atributos.
2. Validación de la calidad de los datos¶
Una vez hallamos importado las librerías y el archivo a emplear para el análisis, validaremos cuál es la calidad que presentan dichos datos con el fin de identificar si hay problemas presentes en las dimensiones de calidad relacionadas a la completitud, estandarización, duplicidad y consistencia de los datos.
#identificamos cuantos datos vacíos se presenta en cada uno de los atributos del dataset.
data_ordenes.isna().sum()
orden_id 0 order_item_id 0 producto_id 0 vendedor_id 0 fecha_envio_limite 0 precio 0 valor_flete 0 codigo_postal_vendedor 0 ciudad_vendedor 0 departamento_vendedor 0 nombre_categoria_producto 23 longitud_nombre_producto 23 longitud_descripcion_producto 23 cantidad_fotos_producto 23 peso_g_producto 23 longitud_cm_producto 23 altura_cm_producto 23 ancho_cm_producto 23 dtype: int64
#Identificamos el porcentaje de datos vacíos para cada atributo con datos vacíos.
data_ordenes[["nombre_categoria_producto",
"longitud_nombre_producto",
"longitud_descripcion_producto",
"cantidad_fotos_producto",
"peso_g_producto",
"longitud_cm_producto",
"altura_cm_producto",
"ancho_cm_producto"]].isna().sum()/len(data_ordenes)*100
nombre_categoria_producto 0.226959 longitud_nombre_producto 0.226959 longitud_descripcion_producto 0.226959 cantidad_fotos_producto 0.226959 peso_g_producto 0.226959 longitud_cm_producto 0.226959 altura_cm_producto 0.226959 ancho_cm_producto 0.226959 dtype: float64
Conclusión de calidad de los datos en la dimensión de completitud: podemos observar que hay un total de 23 valores vacíos en los últimos 8 atributos del data set, los cuales se relacionan con la descripción de las características del producto vendido, decidimos eliminar los registros con valores faltantes debido a que no representan más del 0.3% de todo el conjunto de datos.
#eliminamos los registros vacíos del conjunto de datos.
data_ordenes.dropna(inplace=True)
#en primer lugar vamos a validar que cada uno de los atributos tenga el tipo de dato definido en el dicionario de datos.
data_ordenes.dtypes
orden_id object order_item_id object producto_id object vendedor_id object fecha_envio_limite object precio float64 valor_flete float64 codigo_postal_vendedor int64 ciudad_vendedor object departamento_vendedor object nombre_categoria_producto object longitud_nombre_producto float64 longitud_descripcion_producto float64 cantidad_fotos_producto float64 peso_g_producto float64 longitud_cm_producto float64 altura_cm_producto float64 ancho_cm_producto float64 dtype: object
Tipos de datos: observamos que para el caso de la columna "fecha_envio_limite" el tipo de dato no coincide con el descrito en el diccionario de datos. Por lo tanto debemos hacer a conversión del tipo de dato de esta columna a datetime.
#cambiamos el formato de la columna "fecha_envio_limite" a datetime.
data_ordenes['fecha_envio_limite']=pd.to_datetime(data_ordenes['fecha_envio_limite'],format="%d/%m/%Y %H:%M")
#evidenciamos que el cambio se realizó
data_ordenes[["fecha_envio_limite"]].dtypes
fecha_envio_limite datetime64[ns] dtype: object
#A simple vista no se observa que hayan problemas de calidad en los datos relacionados con la estandarización a parte del
#tipo de dato de la columna "fecha_envio_limite".Haremos un recuento de las categorías en los atributos categóricos
#para verificar más a fondo si existen o no más problemas en la estandarización.
#seleccionamos sólo los atributos categóricos presentes en el dataset.
data_catego=data_ordenes.select_dtypes(include=["object"])
#Revisamos las categorías presentes en cada una de los atributos categóricos.
for i in data_catego.columns:
print(data_catego[i].value_counts().reset_index(),"\n")
orden_id count
0 107500PO59A 1
1 48085PT66A 1
2 24209PR85B 1
3 36058PB41A 1
4 16627PF73A 1
... ... ...
10106 66486PD96A 1
10107 7966PM62A 1
10108 71397PC34A 1
10109 105032PF27A 1
10110 48674PC53A 1
[10111 rows x 2 columns]
order_item_id count
0 A 8861
1 B 906
2 C 188
3 D 80
4 E 34
5 F 22
6 G 5
7 H 4
8 I 4
9 J 3
10 M 1
11 O 1
12 N 1
13 L 1
producto_id count
0 PO55 15
1 PB63 15
2 PF78 14
3 PM57 13
4 PB32 13
... ... ...
1788 PR62 1
1789 PK48 1
1790 PB84 1
1791 PF58 1
1792 PF11 1
[1793 rows x 2 columns]
vendedor_id count
0 VE3276 117
1 VE8511 115
2 VE5049 111
3 VE3791 72
4 VE3215 69
.. ... ...
192 VE1340 35
193 VE9276 35
194 VE8942 33
195 VE3159 31
196 VE8903 30
[197 rows x 2 columns]
ciudad_vendedor count
0 Gualmatan 428
1 Pereira 405
2 Villavicencio 404
3 Manizales 376
4 Acacias 357
.. ... ...
66 Ospina 15
67 Abriaqui 15
68 Acandi 13
69 Acevedo 12
70 Neiva 4
[71 rows x 2 columns]
departamento_vendedor count
0 Nariño 3642
1 Meta 1257
2 Norte de santander 469
3 Risaralda 405
4 Caldas 376
5 Antioquia 363
6 Valle del cauca 338
7 Sucre 287
8 Atlantico 224
9 Casanare 224
10 Cordoba 215
11 Bolivar 194
12 Vaupes 187
13 Cauca 185
14 Bogota d.c. 178
15 Santander 166
16 Guainia 158
17 Choco 153
18 Amazonas 145
19 Arauca 130
20 Caqueta 116
21 La guajira 110
22 Cundinamarca 101
23 Tolima 95
24 Guaviare 92
25 Cesar 72
26 Boyaca 62
27 San andres 41
28 Quindio 34
29 Putumayo 28
30 Magdalena 26
31 Vichada 22
32 Huila 16
nombre_categoria_producto count
0 Tecnología 542
1 Deportes 537
2 Frutas y verduras 531
3 Carnicería 523
4 Ropa de adultos 521
5 Ferretería 518
6 Juguetes 516
7 Ropa infantil 511
8 Electrodomésticos 509
9 Dormitorio 508
10 Lácteos 499
11 Libros 497
12 Productos ecoamigables 497
13 Mascotas 495
14 Tarjetas regalo 495
15 Salud 488
16 Celulares 488
17 Muebles 486
18 Licor 475
19 Bebés 475
Conclusión de calidad de los datos en la dimensión de completitud: vemos que además del problema con el tipo de dato de la columna de fechas de envío, no se presentan más problemas de estandarización, pues observamos que no hay repetición de categorías por errores en el formato de escritura de estás.
#en primer lugar vamos a revisar si hay presencia de duplicados en el identificador único de la orden de venta.
val_uniq=len(data_ordenes["orden_id"].unique())
val_dupli=data_ordenes.duplicated("orden_id").sum()
print("valores únicos:",val_uniq)
print("Valores duplicados en identificador único:",val_dupli)
valores únicos: 10111 Valores duplicados en identificador único: 0
#vamos a revisar si hay valores duplicados parcialmente en todo el conjunto de datos.
print("Valores duplicados en el data set:",data_ordenes.duplicated().sum())
#Revisamos la proporción de duplicidad en los atributos del dataset
data_ordenes[data_ordenes.duplicated()].count()
Valores duplicados en el data set: 0
orden_id 0 order_item_id 0 producto_id 0 vendedor_id 0 fecha_envio_limite 0 precio 0 valor_flete 0 codigo_postal_vendedor 0 ciudad_vendedor 0 departamento_vendedor 0 nombre_categoria_producto 0 longitud_nombre_producto 0 longitud_descripcion_producto 0 cantidad_fotos_producto 0 peso_g_producto 0 longitud_cm_producto 0 altura_cm_producto 0 ancho_cm_producto 0 dtype: int64
Conclusión de calidad de los datos en la dimensión de duplicidad: observamos que no hay presencia de registros duplicados parcialmente o completamente en el conjunto de datos, y lo comprobamos tanto a nivel de identificiador único como a nivel global del set de datos.
#Revisamos de manera rápida las variables estadísticas de los atributos numéricos con el fin de determinar si hay inconsistencia.
data_ordenes.describe()
| fecha_envio_limite | precio | valor_flete | codigo_postal_vendedor | longitud_nombre_producto | longitud_descripcion_producto | cantidad_fotos_producto | peso_g_producto | longitud_cm_producto | altura_cm_producto | ancho_cm_producto | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 10111 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.00000 |
| mean | 2018-01-03 05:02:30.406488064 | 119.868468 | 20.062207 | 49995.607457 | 20.070023 | 19.844724 | 20.097616 | 2106.475522 | 30.067352 | 17.911186 | 22.91257 |
| min | 2017-07-13 22:38:00 | 0.010000 | 0.000000 | 5001.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.00000 |
| 25% | 2017-10-08 11:40:00 | 34.175000 | 5.760000 | 50001.000000 | 10.000000 | 10.000000 | 10.000000 | 607.000000 | 19.000000 | 9.000000 | 15.00000 |
| 50% | 2018-01-03 06:13:00 | 83.180000 | 13.990000 | 52323.000000 | 20.000000 | 20.000000 | 20.000000 | 1459.000000 | 30.000000 | 17.000000 | 23.00000 |
| 75% | 2018-04-01 03:34:30 | 165.640000 | 27.475000 | 54003.000000 | 30.000000 | 30.000000 | 30.000000 | 2941.000000 | 41.000000 | 26.000000 | 30.00000 |
| max | 2018-06-26 02:19:00 | 1262.940000 | 183.150000 | 99001.000000 | 40.000000 | 40.000000 | 40.000000 | 20661.000000 | 91.000000 | 64.000000 | 72.00000 |
| std | NaN | 119.386311 | 20.111832 | 22461.742889 | 11.719844 | 11.851631 | 11.802373 | 2094.402206 | 15.286299 | 11.359838 | 11.15189 |
Conclusión de calidad de los datos en la dimensión de consistencia: podemos observar que los valores mínimos de la mayoría de categorías son 0.0 y para el caso del precio del producto es de 0.010 lo que nos hace preguntarnos si es un error del precio real del producto o si es el precio verdadero; lo mismo sucede con el atributo "codigo_postal_vendedor" en dónde podemos evidenciar que hay códigos postales con un número de dígitos menor a 5. Estas tres situaciones podrían tratarse de datos atípicos por errosres de digitación de la información de registro y deberán ser validadas con el negocio para determinar como poceder con dichos valores en caso de que sean un problema. Mientras tanto, podemos profundizar más en este problema de consistencia de datos numéricos con el objetivo de revisar que tanto se repite esta posible inconsitencia y dar un primer acercamiento a saber si son o no problemas de calidad.
#revisemos con más detalle como se comportan algunas de las columnas de precio, longitud_nombre_producto y codigo_postal_vendedor.
data_ordenes[["precio"]].value_counts().reset_index().sort_values(by="precio")
| precio | count | |
|---|---|---|
| 580 | 0.01 | 2 |
| 6713 | 0.03 | 1 |
| 249 | 0.04 | 3 |
| 5987 | 0.05 | 1 |
| 5986 | 0.08 | 1 |
| ... | ... | ... |
| 3487 | 852.29 | 1 |
| 3486 | 884.85 | 1 |
| 3485 | 995.58 | 1 |
| 3484 | 1003.57 | 1 |
| 8378 | 1262.94 | 1 |
8379 rows × 2 columns
Análisis del atributo precio: vemos que el precio va aumentando gradualmente desde 0.01 y va incrementando el número de cifras de una manera progresiva; vemos que no es un caso aislado el hecho de que el precio tenga un valor mínimo tan bajo. De igual manera es importante confirmar con el negocio si estos valores son o no erroneos.
#revisamos con más detalle el atributo asociado al código postal del vendedor.
data_ordenes[["codigo_postal_vendedor"]].value_counts().reset_index().sort_values(by="codigo_postal_vendedor")
| codigo_postal_vendedor | count | |
|---|---|---|
| 15 | 5001 | 204 |
| 30 | 5002 | 144 |
| 67 | 5004 | 15 |
| 12 | 8001 | 224 |
| 21 | 11001 | 178 |
| ... | ... | ... |
| 29 | 91001 | 145 |
| 25 | 94001 | 158 |
| 45 | 95001 | 92 |
| 18 | 97001 | 187 |
| 64 | 99001 | 22 |
71 rows × 2 columns
#identificamos las ciudades a las que representan dichos códigos postales.
data_codigo=data_ordenes.groupby(["codigo_postal_vendedor","ciudad_vendedor"]).count()[
"departamento_vendedor"].reset_index(name="Conteo")
data_codigo
| codigo_postal_vendedor | ciudad_vendedor | Conteo | |
|---|---|---|---|
| 0 | 5001 | Medellin | 204 |
| 1 | 5002 | Abejorral | 144 |
| 2 | 5004 | Abriaqui | 15 |
| 3 | 8001 | Barranquilla | 224 |
| 4 | 11001 | Bogota d.c. | 178 |
| ... | ... | ... | ... |
| 66 | 91001 | Leticia | 145 |
| 67 | 94001 | Inirida | 158 |
| 68 | 95001 | San jose del guaviare | 92 |
| 69 | 97001 | Mitu | 187 |
| 70 | 99001 | Puerto carreño | 22 |
71 rows × 3 columns
#verificamos si hay tantos códigos postales como ciudades en el conjunto de datos, determinando la
#cantidad de valores únicos en cada atrinuto
print("Valores únicos de códigos postales:",data_codigo["codigo_postal_vendedor"].unique().shape)
print("Valores únicos ciudades:",data_codigo["ciudad_vendedor"].unique().shape)
Valores únicos de códigos postales: (71,) Valores únicos ciudades: (71,)
Análisis del atributo codigo_postal_vendedor: podemos observar que hay 4 cados dónde el código postal tiene solo 4 digítos, que no es solo el caso del valor mínimo, por lo cual este caso no es aislado. Para profundizar en este caso comparamos el código postal del vendedor junto con el nombre de la ciudad de este, con el fin de determinar si estos código de 4 dígitos son correctos o es por una omisión de algún dígito al momento del registro. Observamos que la cantidad de valores únicos en cada atributo son iguales, por lo cual se descarta la posibilidad de los códigos con un número de 4 dígitos sean errores de consistencia.
#revisamos con más detalle el atributo asociado a la longitud del nombre del producto.
data_ordenes[["longitud_nombre_producto"]].value_counts().reset_index().sort_values(by="longitud_nombre_producto").head(15)
| longitud_nombre_producto | count | |
|---|---|---|
| 40 | 0.0 | 207 |
| 32 | 1.0 | 233 |
| 39 | 2.0 | 224 |
| 17 | 3.0 | 247 |
| 35 | 4.0 | 230 |
| 21 | 5.0 | 245 |
| 22 | 6.0 | 244 |
| 0 | 7.0 | 271 |
| 4 | 8.0 | 266 |
| 5 | 9.0 | 265 |
| 6 | 10.0 | 263 |
| 10 | 11.0 | 260 |
| 25 | 12.0 | 241 |
| 38 | 13.0 | 227 |
| 31 | 14.0 | 236 |
Análisis del atributo longitud_nombre_producto: observamos que en total hay 207 productos que aparentemente no tienen un nombre asociado en los registros, un total de 233 que tienen por nombre un solo caracter y 224 que tienen solo 2 caracteres en su nombre. En primer lugar, lo ideal, sería validar con el negocio a ver si esta información se refleja de igual manera en la plataforma digital o si es un error al momento de descargar el conjunto de datos. Del mismo modo se deberán validar los valores que parecen ser atípicos en las otras categorías de las características del producto.
3. Análisis estadístico univariado¶
Objetivos¶
- Determinar cual es el comportamiento estadístico de los atributos numéricos del dataset.
- Identificar cuales son las categorías de producto más vendidas a nivel nacional y cuales son aquellas que componen el 20% de las ventas principales del negocio con el fin de aplicar un descuento al precio de venta sobre estas.
- Determinar cual el porcentaje de los productos que pesan menos de 1000 g con el fin de aplicar un descuento sobre el valor del flete de estos productos.
#revisamos las principales variables estadísticas del conjunto de datos, tales como media, desviación estandar,
#y percentiles del 25, 50 y 75%.
data_ordenes.describe()
| fecha_envio_limite | precio | valor_flete | codigo_postal_vendedor | longitud_nombre_producto | longitud_descripcion_producto | cantidad_fotos_producto | peso_g_producto | longitud_cm_producto | altura_cm_producto | ancho_cm_producto | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 10111 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.000000 | 10111.00000 |
| mean | 2018-01-03 05:02:30.406488064 | 119.868468 | 20.062207 | 49995.607457 | 20.070023 | 19.844724 | 20.097616 | 2106.475522 | 30.067352 | 17.911186 | 22.91257 |
| min | 2017-07-13 22:38:00 | 0.010000 | 0.000000 | 5001.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.00000 |
| 25% | 2017-10-08 11:40:00 | 34.175000 | 5.760000 | 50001.000000 | 10.000000 | 10.000000 | 10.000000 | 607.000000 | 19.000000 | 9.000000 | 15.00000 |
| 50% | 2018-01-03 06:13:00 | 83.180000 | 13.990000 | 52323.000000 | 20.000000 | 20.000000 | 20.000000 | 1459.000000 | 30.000000 | 17.000000 | 23.00000 |
| 75% | 2018-04-01 03:34:30 | 165.640000 | 27.475000 | 54003.000000 | 30.000000 | 30.000000 | 30.000000 | 2941.000000 | 41.000000 | 26.000000 | 30.00000 |
| max | 2018-06-26 02:19:00 | 1262.940000 | 183.150000 | 99001.000000 | 40.000000 | 40.000000 | 40.000000 | 20661.000000 | 91.000000 | 64.000000 | 72.00000 |
| std | NaN | 119.386311 | 20.111832 | 22461.742889 | 11.719844 | 11.851631 | 11.802373 | 2094.402206 | 15.286299 | 11.359838 | 11.15189 |
A partir de esta primera descripción estadísticas de los datos podemos tener un acercamiento a la forma de distribución de los atributos. Al observar el valor del percentil 50 y el valor máximo del precio de venta, nos damos cuenta que los precios de ventas presentan una asimetría positiva debido a que los datos se concentran en mayor cantidad a la izquierda con un sesgo a la derecha; este también es el caso para el valor del flete, el peso del producto vendido y y las dimensiones del producto. Sin embargo, la concentración de los datos hacia la izquierda es mucho mayor en los atributos de precio, valor del flete y peso del producto.
Para el caso de los atributos relacionados con las características del nombre del producto y la descripción de este, de acuerdo con el valor de su mediana y el valor máximo registrado, se observa que presentan una distribución simétrica.
Contexto: desde el negocio están interesados en conocer cuáles son las categorías de productos más vendidas a nivel nacional, del mismo modo que conocer cuales son aquellas categorías que contribuyen al 20% de las ventas principales en el negocio, esto con el fin de diseñar una estrategia de negocio enfocada en dichas categorías en la que se buscará realizar un descuento sobre estas.
#Para conocer cuáles son las categorías de productos más vendidas calcularemos la distribución de frecuencias de este atributo.
#Calculamos la frecuencia absoluta de las distintas categorías.
frec_productos=data_ordenes['nombre_categoria_producto'].value_counts().reset_index()
frec_productos=frec_productos.rename(columns={'count':'fi'})
#Calculamos la frecuencia relativa.
frec_productos['hi %']=frec_productos['fi'].apply(lambda x:((x/frec_productos['fi'].sum())*100).round(1))
#Calculamos ahora la frecuencia absoluta acumulada y relativa acumulada.
frec_productos['Fi']=frec_productos['fi'].cumsum() #frecuencia absoluta acumulada.
frec_productos['Hi %']=frec_productos['hi %'].cumsum() #frecuencia relativa acumulada.
frec_productos
| nombre_categoria_producto | fi | hi % | Fi | Hi % | |
|---|---|---|---|---|---|
| 0 | Tecnología | 542 | 5.4 | 542 | 5.4 |
| 1 | Deportes | 537 | 5.3 | 1079 | 10.7 |
| 2 | Frutas y verduras | 531 | 5.3 | 1610 | 16.0 |
| 3 | Carnicería | 523 | 5.2 | 2133 | 21.2 |
| 4 | Ropa de adultos | 521 | 5.2 | 2654 | 26.4 |
| 5 | Ferretería | 518 | 5.1 | 3172 | 31.5 |
| 6 | Juguetes | 516 | 5.1 | 3688 | 36.6 |
| 7 | Ropa infantil | 511 | 5.1 | 4199 | 41.7 |
| 8 | Electrodomésticos | 509 | 5.0 | 4708 | 46.7 |
| 9 | Dormitorio | 508 | 5.0 | 5216 | 51.7 |
| 10 | Lácteos | 499 | 4.9 | 5715 | 56.6 |
| 11 | Libros | 497 | 4.9 | 6212 | 61.5 |
| 12 | Productos ecoamigables | 497 | 4.9 | 6709 | 66.4 |
| 13 | Mascotas | 495 | 4.9 | 7204 | 71.3 |
| 14 | Tarjetas regalo | 495 | 4.9 | 7699 | 76.2 |
| 15 | Salud | 488 | 4.8 | 8187 | 81.0 |
| 16 | Celulares | 488 | 4.8 | 8675 | 85.8 |
| 17 | Muebles | 486 | 4.8 | 9161 | 90.6 |
| 18 | Licor | 475 | 4.7 | 9636 | 95.3 |
| 19 | Bebés | 475 | 4.7 | 10111 | 100.0 |
Productos principales: podemos observar que a nivel nacional los productos contribuyen a las ventas de una forma casi uniforme, donde cada uno de estos productos tiene un peso en las ventas de aproximadamente el 5%, por lo cual podría decirse que a pesar de que hay algunas categorías que son primeras en ventas, el 50% de las ventas del negocio será alcanzado siempre con la mitad de las categorías existentes, sin importar, cual de ellas sean escogidas, de acuerdo con los datos actuales.
Por otro lado, las categorías de Tecnología, Deportes, Frutas y verduras, y Carnicería deberán ser las escogidas para aplicar la estaregia de descuento si se quiere impactar sobre el 20% principal de las ventas.
#vamos a visualizar en un gráfico de pastel como es el comportamiento de las ventas a nivel nacional.
fig = px.pie(frec_productos,values="fi",names="nombre_categoria_producto",
title="Comportamiento de las ventas a nivel nacional")
Contexto: desde el negocio se ha decido que se quiere aplicar una estrategia de negocio adicional, y para esto desean realizar una campaña de descuento enfocada en el valor del flete de envío de los productos, este descuento en alianza con la empresa de logística, se podrá aplicar solo a aquellos productos que tengan un peso menor a 1000 g.
De acuerdo a lo anterior, la empresa desea conocer cuál es el porcentaje de los productos vendidos al cual se le podrá aplicar dicho descuento y conocer las categorías que se verán impactas.
#a partir de la descripción estadística previa realizada sobre los datos, sabemos que el porcentaje de productos con un peso
#menor o igual a 1000 gr se encuentra entre 25% y 50%. Por ello procedemos a encontrar el percentil en este rango.
#creamos un diccionario para guardar los resultados de percentil y peso del producto.
percentiles={}
rango_min=0.25 #rango de percentil mínimo de búsqueda.
rango_max=0.50 #rango de percentil máximo de búsqueda.
valor_deseado=1000 #peso máximo permitido.
#creamos un ciclo que itere entre el rango propuesto.
for i in np.arange(rango_min,rango_max,0.01):
if (data_ordenes["peso_g_producto"].quantile(i) - valor_deseado) <= 0:
#guardamos todos los resultados menores o iguales a 1000 g.
percentiles[round(i,2)]=round(data_ordenes["peso_g_producto"].quantile(i),0)
#imprimimos el percentil que proporciona el peso de producto más cercano a 1000 g.
print("El porcentaje de productos con un peso menor o igual a 1000 es de",max(percentiles.keys())*100,"%")
El porcentaje de productos con un peso menor o igual a 1000 es de 37.0 %
#Encontremos aquellas categorías que tienen productos con un peso menor a 1000 g y realizamos un conteo de productos.
prod_descuento=data_ordenes.loc[data_ordenes["peso_g_producto"]<=1000,
"nombre_categoria_producto"].value_counts().reset_index(name="En descuento")
#adicionamos la frecuencia absoluta de cada categoría en el conjunto de datos total.
prod_descuento=pd.merge(prod_descuento,frec_productos[["nombre_categoria_producto","fi"]],
on="nombre_categoria_producto",how="inner")
#calculamos que % de productos están en descuento dentro de cada categoría.
prod_descuento["% de categoría"]=round((prod_descuento["En descuento"]/prod_descuento['fi'])*100,0)
#calculamos el % de productos por cada categoría que está en descuendo dentro del total de productos en venta.
prod_descuento["% de productos"]=round((prod_descuento["En descuento"]/prod_descuento['fi'].sum())*100,1)
prod_descuento
| nombre_categoria_producto | En descuento | fi | % de categoría | % de productos | |
|---|---|---|---|---|---|
| 0 | Ropa de adultos | 210 | 521 | 40.0 | 2.1 |
| 1 | Electrodomésticos | 202 | 509 | 40.0 | 2.0 |
| 2 | Mascotas | 200 | 495 | 40.0 | 2.0 |
| 3 | Carnicería | 199 | 523 | 38.0 | 2.0 |
| 4 | Deportes | 199 | 537 | 37.0 | 2.0 |
| 5 | Libros | 197 | 497 | 40.0 | 1.9 |
| 6 | Tecnología | 195 | 542 | 36.0 | 1.9 |
| 7 | Frutas y verduras | 194 | 531 | 37.0 | 1.9 |
| 8 | Tarjetas regalo | 194 | 495 | 39.0 | 1.9 |
| 9 | Juguetes | 192 | 516 | 37.0 | 1.9 |
| 10 | Dormitorio | 192 | 508 | 38.0 | 1.9 |
| 11 | Muebles | 191 | 486 | 39.0 | 1.9 |
| 12 | Lácteos | 191 | 499 | 38.0 | 1.9 |
| 13 | Productos ecoamigables | 191 | 497 | 38.0 | 1.9 |
| 14 | Ferretería | 190 | 518 | 37.0 | 1.9 |
| 15 | Celulares | 189 | 488 | 39.0 | 1.9 |
| 16 | Salud | 181 | 488 | 37.0 | 1.8 |
| 17 | Ropa infantil | 175 | 511 | 34.0 | 1.7 |
| 18 | Licor | 167 | 475 | 35.0 | 1.7 |
| 19 | Bebés | 164 | 475 | 35.0 | 1.6 |
#visualicemos la cantidad de productos en descuento.
#creamos un dataframe para identificar el total de productos en descuentos y el total de productos que no están en descuento.
proporcion_desc=pd.DataFrame()
proporcion_desc["Estado del producto"]="En descuento","Sin descuento"
proporcion_desc["Cantidad"] = [(prod_descuento["En descuento"].sum()),
(-prod_descuento["En descuento"].sum()+prod_descuento['fi'].sum())]
#construimos el gráfico de pastel para visualizar los datos.
fig1 = px.pie(proporcion_desc,values="Cantidad",names="Estado del producto",
title="Proporción de productos en descuento")
#vamos a visualizar en un gráfico de pastel como se distribuye el descuento en productos con un peso menor a 1000 g.
fig2 = px.pie(prod_descuento,values="En descuento",names="nombre_categoria_producto",
title="Contribución de cada categoría a los productos en descuento")
#vamos a visualizar en un gráfico de barras como se distribuye el descuento a nivel de categorías.
#creamos colores aleatorias para la figura.
get_colors = lambda n: list(map(lambda i: "#" + "%06x" % random.randint(0, 0xFFFFFF),range(n)))
colores=get_colors(len(prod_descuento))
#creamos la figura con Plotly.
fig3 = px.bar(prod_descuento,x="% de categoría",y="nombre_categoria_producto",color="nombre_categoria_producto",
color_discrete_sequence=colores,title="Proporción de productos en descuento según su categoría")
#cambiamos el grosor de las barras para que sean más legibles.
for bar in fig3.data:
bar.width = 0.4
#vamos a visualizar en un gráfico de barras como se distribuye el descuento a nivel total de productos constrastado con a nivel
#de categorías.
fig4 = px.bar(prod_descuento,y=["% de productos","% de categoría"],x="nombre_categoria_producto",barmode="group",
title="Proporción de productos en descuento")
#cambiamos el grosor de las barras para que sean más legibles.
for bar in fig4.data:
bar.width = 0.4
Cantidad de productos aceptables: la estrategia de negocio basada en un descuento al valor del envío se podrá aplicar solo al 37% de los productos y se podrán incluir en el descuento todas las categorías de venta del negocio. Los productos que estarán en descuentos representan para cada categoría entre un 35 y 40% de su totalidad; y representan entre un 1.6 y 2.1% del total de productos vendidos en el negocio por cada categoría en descuento.
fig.show(),fig1.show(),fig2.show(),fig3.show(),fig4.show()
(None, None, None, None, None)
Conclusión del análisis de negocio¶
Al describir los datos de manera estadística y al observar el valor del percentil 50 y el valor máximo del precio de venta, nos dimos cuenta que los precios de venta presentan una asimetría positiva debido a que los datos se concentran en mayor cantidad a la izquierda con un sesgo a la derecha; este también es el caso para el valor del flete, el peso del producto vendido y y las dimensiones del producto. Sin embargo, la concentración de los datos hacia la izquierda es mucho mayor en los atributos de precio, valor del flete y peso del producto.
Para el caso de los atributos relacionados con las características del nombre del producto y la descripción de este, de acuerdo con el valor de su mediana y el valor máximo registrado, se observa que presentan una distribución simétrica.
Desde el punto de vista del negocio, podemos observar que a nivel nacional los productos contribuyen a las ventas de una forma casi uniforme, donde cada uno de estos productos tiene un peso en las ventas de aproximadamente el 5%, por lo cual podría decirse que a pesar de que hay algunas categorías que son primeras en ventas, el 50% de las ventas del negocio será alcanzado siempre con la mitad de las categorías existentes, sin importar, cual de ellas sean escogidas, de acuerdo con los datos actuales.
Como estrategia de negocio, las categorías de Tecnología, Deportes, Frutas y verduras, y Carnicería deberán ser las escogidas para aplicar la estaregia de descuento sobre el precio de producto si se quiere impactar sobre el 20% principal de las ventas. Adicionalmente, la estrategia de negocio basada en un descuento al valor del envío se podrá aplicar solo al 37% de los productos y se podrán incluir en el descuento todas las categorías de venta del negocio. Los productos que estarán en descuentos representan para cada categoría entre un 35 y 40% de su totalidad; y representan entre un 1.6 y 2.1% del total de productos vendidos en el negocio por cada categoría en descuento.